﻿<!--
> Muaz Khan     - www.MuazKhan.com
> MIT License   - www.WebRTC-Experiment.com/licence
> Experiments   - github.com/muaz-khan/WebRTC-Experiment
-->
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>How to use RTCDataChannel? | Muaz Khan</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
        <link rel="author" type="text/html" href="https://plus.google.com/+MuazKhan">
        <meta name="author" content="Muaz Khan">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        
        <link rel="stylesheet" href="https://cdn.webrtc-experiment.com/style.css">
        
        <style>
            p { padding: 1em; }

            li {
                border-bottom: 1px solid rgb(189, 189, 189);
                border-left: 1px solid rgb(189, 189, 189);
                padding: .5em;
            }

            .experiment .experiment { margin: 1em; }
        </style>
        <script>
            document.createElement('article');
            document.createElement('footer');
        </script>>
    
        <script type="text/javascript" src="https://cdn.webrtc-experiment.com/syntax/sh_main.min.js"> </script>
        <script type="text/javascript" src="https://cdn.webrtc-experiment.com/syntax/sh_javascript.min.js"> </script>
        <script type="text/javascript" src="https://cdn.webrtc-experiment.com/syntax/sh_html.min.js"> </script>
        <link type="text/css" rel="stylesheet" href="https://cdn.webrtc-experiment.com/syntax/sh_style.css">
		
    </head>

    <body onload="sh_highlightDocument();">
        <article>
            <header style="text-align: center;">
                <h1>
                    How to use RTCDataChannel?
                </h1>            
                <p>
                    <a href="https://www.webrtc-experiment.com/">HOME</a>
                    <span> &copy; </span>
                    <a href="http://www.MuazKhan.com/" target="_blank">Muaz Khan</a>
                    
                    .
                    <a href="http://twitter.com/WebRTCWeb" target="_blank" title="Twitter profile for WebRTC Experiments">@WebRTCWeb</a>
                    
                    .
                    <a href="https://github.com/muaz-khan?tab=repositories" target="_blank" title="Github Profile">Github</a>
                    
                    .
                    <a href="https://github.com/muaz-khan/WebRTC-Experiment/issues?state=open" target="_blank">Latest issues</a>
                    
                    .
                    <a href="https://github.com/muaz-khan/WebRTC-Experiment/commits/master" target="_blank">What's New?</a>
                </p>
            </header>

            <div class="github-stargazers"></div>

            <blockquote style="background: #f3b7b7;border: 5px solid black;border-radius: 5px;">
                This tutorial is <span style="border: 1px dotted red; background: yellow; padding: 2px 5px;">out-dated (written in 2013)</span>. Please check this tutorial instead: <a href="https://codelabs.developers.google.com/codelabs/webrtc-web/#0">https://codelabs.developers.google.com/codelabs/webrtc-web/#0</a>
            </blockquote>
            
            <section class="experiment">
                <h2 class="header">This document guides you through:</h2>
                <ol>
                    <li><a href="#sctp-chrome">How to setup SCTP data connection</a> (for chrome)?</li>
                    <li><a href="#sctp-firefox">How to setup SCTP data connection</a> (for firefox)?</li>
                    <li><a href="#rtp-chrome">How to setup RTP data connection</a> (for older chrome releases)?</li>
                </ol>
            </section>
            
            <section class="experiment">
                <h2 class="header">A few terminologies:</h2>
                <ol>
                    <li>There is always an "offerer".</li>
                    <li>There is always an "answerer".</li>
                    <li>"Offerer" creates offer; it must be shared with "answerer".</li>
                    <li>"answerer" sets his remote session-descriptions based on "offer-sdp" provided by offerer.</li>
                    <li>"answerer" creates answer; it must be shared with "Offerer".</li>
                    <li>"Offerer" sets his remote session-descriptions based on "answerer-sdp" provided by answerer.</li>
                </ol>
            </section>
            
            <section class="experiment">
                <h2 class="header" id="sctp-chrome">SCTP data-connection and Chrome!</h2>
                <p>
                    Setting global variables used by both offerer and answerer.
                </p>
                
                <section class="experiment">
                    <h2 class="header">Global variables:</h2>
                    <pre class="sh_javascript">
var iceServers = {
    iceServers: [{
        url: 'stun:stun.l.google.com:19302'
    }]
};
var offererDataChannel, answererDataChannel;
</pre>
                </section>
                
                <p>
                    Creating two objects; one for offerer; and other for answerer.
                </p>
                
                <section class="experiment">
                    <h2 class="header">Object for offerer:</h2>
                    <pre class="sh_javascript">
var Offerer = {
    createOffer: function () {
        var peer = new webkitRTCPeerConnection(iceServers);

        offererDataChannel = peer.createDataChannel('channel', {});
        setChannelEvents(offererDataChannel);

        peer.onicecandidate = function (event) {
            if (event.candidate)
                Send_to_Other_Peer(event.candidate);
        };

        peer.createOffer(function (sdp) {
            peer.setLocalDescription(sdp);
            Send_to_Other_Peer(sdp);
        });

        this.peer = peer;

        return this;
    },
    setRemoteDescription: function (sdp) {
        this.peer.setRemoteDescription(new RTCSessionDescription(sdp));
    },
    addIceCandidate: function (candidate) {
        this.peer.addIceCandidate(new RTCIceCandidate({
            sdpMLineIndex: candidate.sdpMLineIndex,
            candidate: candidate.candidate
        }));
    }
};
</pre>
                </section>
                
                <section class="experiment">
                    <h2 class="header">Object for answerer:</h2>
                    <pre class="sh_javascript">
var Answerer = {
    createAnswer: function (offerSDP) {
        var peer = new RTCPeerConnection(iceServers);
        peer.ondatachannel = function (event) {
            answererDataChannel = event.channel;
            setChannelEvents(answererDataChannel);
        };

        peer.onicecandidate = function (event) {
            if (event.candidate)
                Send_to_Other_Peer(event.candidate);
        };

        peer.setRemoteDescription(new RTCSessionDescription(offerSDP));
        peer.createAnswer(function (sdp) {
            peer.setLocalDescription(sdp);
            Send_to_Other_Peer(sdp);
        });

        this.peer = peer;

        return this;
    },
    addIceCandidate: function (candidate) {
        this.peer.addIceCandidate(new RTCIceCandidate({
            sdpMLineIndex: candidate.sdpMLineIndex,
            candidate: candidate.candidate
        }));
    }
};
</pre>
                </section>
                
                <section class="experiment">
                    <h2 class="header">setChannelEvents function:</h2>
                    <pre class="sh_javascript">
function setChannelEvents(channel) {
    channel.onmessage = function (event) {
        var data = JSON.parse(event.data);
        console.log(data);
    };
    channel.onopen = function () {
        channel.push = channel.send;
        channel.send = function (data) {
            channel.push(JSON.stringify(data));
        };
    };

    channel.onerror = function (e) {
        console.error('channel.onerror', JSON.stringify(e, null, '\t'));
    };

    channel.onclose = function (e) {
        console.warn('channel.onclose', JSON.stringify(e, null, '\t'));
    };
}
</pre>

                </section>
                <p>
                    Now, you've two objects; one for offerer; and last one for answerer. You can execute/call them according to the situation of the user.
                </p>
                
                <section class="experiment">
                    <h2 class="header">If a user is offerer:</h2>
                    <pre class="sh_javascript">
var offerer = Offerer.createOffer();
</pre>
                </section>
            
                <section class="experiment">
                    <h2 class="header">If a user is answerer:</h2>
                    <pre class="sh_javascript">
var answerer = Answerer.createAnswer(offerSDP);
</pre>
                </section>
            
                <section class="experiment">
                    <h2 class="header">A simple example:</h2>
                    <pre class="sh_javascript">
var websocket = new WebSocket('ws://localhost:8888/');

websocket.onmessage = function (e) {
    e = JSON.parse(e.data);

    // Don't get self sent messages
    if (e.senderid == userid) return;

    var data = e.data;

    // if other user created offer; and sent you offer-sdp
    if (data.offerSDP) {
        window.answerer = Answerer.createAnswer(data.offerSDP);
    }

    // if other user created answer; and sent you answer-sdp
    if (data.answerSDP) {
        window.offerer.setRemoteDescription(data.answerSDP);
    }

    // if other user sent you ice candidates
    if (data.ice) {
        // it will be fired both for offerer and answerer
        (window.answerer || window.offerer).addIceCandidate(data.ice);
    }
};

var userid = Math.random() * 1000;

websocket.push = websocket.send;
websocket.send = function (data) {
    // wait/loop until socket connection gets open
    if (websocket.readState != 1) {
        // websocket connection is not opened yet.
        return setTimeout(function () {
            websocket.send(data);
        }, 500);
    }

    // data is stringified because websocket protocol accepts only string data
    var json_stringified_data = JSON.stringify({
        senderid: userid,
        data: data
    });

    websocket.push(json_stringified_data);
}
</pre>
                </section>
            
            </section>
            
            <section class="experiment">
                <h2 class="header" id="sctp-firefox">SCTP data-connection and Firefox!</h2>
                <p>
                    Setting global variables used by both offerer and answerer.
                </p>
                
                <section class="experiment">
                    <h2 class="header">Global variables:</h2>
                    <pre class="sh_javascript">
var iceServers = {
    iceServers: [{
        url: 'stun:23.21.150.121'
    }]
};

var offerAnswerConstraints = {
    optional: [],
    mandatory: {
        OfferToReceiveAudio: true,
        OfferToReceiveVideo: true
    }
};

var offererDataChannel, answererDataChannel;
</pre>
                </section>
                
                <p>
                    Creating two objects; one for offerer; and other for answerer.
                </p>
                
                <section class="experiment">
                    <h2 class="header">Object for offerer:</h2>
                    <pre class="sh_javascript">
var Offerer = {
    createOffer: function () {
        var peer = new mozRTCPeerConnection(iceServers);

        peer.onicecandidate = function (event) {
            if (event.candidate)
                Send_to_Other_Peer(event.candidate);
        };

        peer.ondatachannel = function (event) {
            offererDataChannel = event.channel;
            setChannelEvents(offererDataChannel);
        };

        peer.onconnection = function () {
            websocket.send({askToCreateDataChannel:true});
        };

        navigator.mozGetUserMedia({
            audio: true,
            fake: true
        }, function (stream) {
            peer.addStream(stream);

            offererDataChannel = peer.createDataChannel('channel', {});
            setChannelEvents(offererDataChannel);

            // create offer after attaching fake media stream
            peer.createOffer(function (sdp) {
                peer.setLocalDescription(sdp);
                Send_to_Other_Peer(sdp);
            });
        }, function(error) {} );

        this.peer = peer;

        return this;
    },
    setRemoteDescription: function (sdp) {
        this.peer.setRemoteDescription(new mozRTCSessionDescription(sdp));
    },
    addIceCandidate: function (candidate) {
        this.peer.addIceCandidate(new mozRTCIceCandidate({
            sdpMLineIndex: candidate.sdpMLineIndex,
            candidate: candidate.candidate
        }));
    }
};
</pre>
                </section>
                
                <section class="experiment">
                    <h2 class="header">Object for answerer:</h2>
                    <pre class="sh_javascript">
var Answerer = {
    createAnswer: function (offerSDP) {
        var peer = new mozRTCPeerConnection(iceServers);
        peer.ondatachannel = function (event) {
            answererDataChannel = event.channel;
            setChannelEvents(answererDataChannel);
        };

        peer.onicecandidate = function (event) {
            if (event.candidate)
                Send_to_Other_Peer(event.candidate);
        };

        navigator.mozGetUserMedia({
            audio: true,
            fake: true
        }, function (stream) {
            peer.addStream(stream);

            // create answer after attaching fake media stream
            peer.setRemoteDescription(new mozRTCSessionDescription(offerSDP));
            peer.createAnswer(function (sdp) {
                peer.setLocalDescription(sdp);
                Send_to_Other_Peer(sdp);
            });

        }, function(error) {});

        this.peer = peer;

        return this;
    },
    addIceCandidate: function (candidate) {
        this.peer.addIceCandidate(new mozRTCIceCandidate({
            sdpMLineIndex: candidate.sdpMLineIndex,
            candidate: candidate.candidate
        }));
    }
};
</pre>
                </section>
                
                <section class="experiment">
                    <h2 class="header">setChannelEvents function:</h2>
                    <pre class="sh_javascript">
function setChannelEvents(channel) {
    channel.onmessage = function (event) {
        var data = JSON.parse(event.data);
        console.log(data);
    };
    channel.onopen = function () {
        channel.push = channel.send;
        channel.send = function (data) {
            channel.push(JSON.stringify(data));
        };
    };

    channel.onerror = function (e) {
        console.error('channel.onerror', JSON.stringify(e, null, '\t'));
    };

    channel.onclose = function (e) {
        console.warn('channel.onclose', JSON.stringify(e, null, '\t'));
    };
}
</pre>

                </section>
                <p>
                    Now, you've two objects; one for offerer; and last one for answerer. You can execute/call them according to the situation of the user.
                </p>
                
                <section class="experiment">
                    <h2 class="header">If a user is offerer:</h2>
                    <pre class="sh_javascript">
var offerer = Offerer.createOffer();
</pre>
                </section>
            
                <section class="experiment">
                    <h2 class="header">If a user is answerer:</h2>
                    <pre class="sh_javascript">
var answerer = Answerer.createAnswer(offerSDP);
</pre>
                </section>
            
                <section class="experiment">
                    <h2 class="header">A simple example:</h2>
                    <pre class="sh_javascript">
var websocket = new WebSocket('ws://localhost:8888/');

websocket.onmessage = function (e) {
    e = JSON.parse(e.data);

    // Don't get self sent messages
    if (e.senderid == userid) return;

    var data = e.data;

    // if other user created offer; and sent you offer-sdp
    if (data.offerSDP) {
        window.answerer = Answerer.createAnswer(data.offerSDP);
    }

    // if other user created answer; and sent you answer-sdp
    if (data.answerSDP) {
        window.offerer.setRemoteDescription(data.answerSDP);
    }

    // if other user sent you ice candidates
    if (data.ice) {
        // it will be fired both for offerer and answerer
        (window.answerer || window.offerer).addIceCandidate(data.ice);
    }
	
    // we need to create data channel for both ends! (on Firefox only)
    if(data.askToCreateDataChannel) {
        answererDataChannel = peer.createDataChannel('channel', {});
        setChannelEvents(answererDataChannel);
    }
};

var userid = Math.random() * 1000;

websocket.push = websocket.send;
websocket.send = function (data) {
    // wait/loop until socket connection gets open
    if (websocket.readState != 1) {
        // websocket connection is not opened yet.
        return setTimeout(function () {
            websocket.send(data);
        }, 500);
    }

    // data is stringified because websocket protocol accepts only string data
    var json_stringified_data = JSON.stringify({
        senderid: userid,
        data: data
    });

    websocket.push(json_stringified_data);
}
</pre>
                </section>
            
            </section>
            
			
            <section class="experiment">
                <h2 class="header" id="rtp-chrome">RTP data-connection and Chrome!</h2>
                <pre class="sh_javascript">
var iceServers = {
    iceServers: [{
        url: 'stun:stun.l.google.com:19302'
    }]
};

var optionalRtpDataChannels = {
    optional: [{
        RtpDataChannels: true
    }]
};

var offerer = new webkitRTCPeerConnection(iceServers, optionalRtpDataChannels),
    answerer, answererDataChannel;

var offererDataChannel = offerer.createDataChannel('RTCDataChannel', {
    reliable: false
});
setChannelEvents(offererDataChannel, 'offerer');

offerer.onicecandidate = function (event) {
    if (!event || !event.candidate) return;
    answerer && answerer.addIceCandidate(event.candidate);
};

var mediaConstraints = {
    optional: [],
    mandatory: {
        OfferToReceiveAudio: false, // Hmm!!
        OfferToReceiveVideo: false // Hmm!!
    }
};

offerer.createOffer(function (sessionDescription) {
    offerer.setLocalDescription(sessionDescription);
    createAnswer(sessionDescription);
}, null, mediaConstraints);


function createAnswer(offerSDP) {
    answerer = new webkitRTCPeerConnection(iceServers, optionalRtpDataChannels);
    answererDataChannel = answerer.createDataChannel('RTCDataChannel', {
        reliable: false
    });

    setChannelEvents(answererDataChannel, 'answerer');

    answerer.onicecandidate = function (event) {
        if (!event || !event.candidate) return;
        offerer && offerer.addIceCandidate(event.candidate);
    };

    answerer.setRemoteDescription(offerSDP);
    answerer.createAnswer(function (sessionDescription) {
        answerer.setLocalDescription(sessionDescription);
        offerer.setRemoteDescription(sessionDescription);
    }, null, mediaConstraints);
}

function setChannelEvents(channel, channelNameForConsoleOutput) {
    channel.onmessage = function (event) {
        console.debug(channelNameForConsoleOutput, 'received a message:', event.data);
    };

    channel.onopen = function () {
        channel.send('first text message over RTP data ports');
    };
    channel.onclose = function (e) {
        console.error(e);
    };
    channel.onerror = function (e) {
        console.error(e);
    };
}
</pre>

                <p>After executing above code in the console; try to send messages over RTP data ports like this: </p>
                <pre class="sh_javascript">
offererDataChannel.send('message from offerer');
answererDataChannel.send('message from answerer');
</pre>
            </section>
        
            <section class="experiment own-widgets latest-commits">
                <h2 class="header" id="updates" style="color: red; padding-bottom: .1em;"><a href="https://github.com/muaz-khan/WebRTC-Experiment/commits/master" target="_blank">Latest Updates</a></h2>
                <div id="github-commits"></div>
            </section>
        
            <section class="experiment">
                <h2 class="header" id="feedback">Feedback</h2>
                <div>
                    <textarea id="message" style="border: 1px solid rgb(189, 189, 189); height: 8em; margin: .2em; outline: none; resize: vertical; width: 98%;" placeholder="Have any message? Suggestions or something went wrong?"></textarea>
                </div>
                <button id="send-message" style="font-size: 1em;">Send Message</button><small style="margin-left: 1em;">Enter your email too; if you want "direct" reply!</small>
            </section>
        </article>
        <a href="https://github.com/muaz-khan/WebRTC-Experiment" class="fork-left"></a>
        
        <footer>
            <p>
                <a href="https://www.webrtc-experiment.com/">WebRTC Experiments</a>
                © <a href="https://plus.google.com/+MuazKhan" rel="author" target="_blank">Muaz Khan</a>
                <a href="mailto:muazkh@gmail.com" target="_blank">muazkh@gmail.com</a>
            </p>
        </footer>
    
        <!-- commits.js is useless for you! -->
        <script src="https://cdn.webrtc-experiment.com/commits.js" async> </script>
    </body>
</html>
